#region File header
// Project: SharpWars.Core, File: Client.cs
// Namespace: SharpWars.Core.Network, Class: Client
// Path: F:\Coder\Projekty\Windows\Gry\SharpWars\SharpWars.Core\Network, Author: Sebastian Haba (sho@gisz.net.pl)
// Code lines: 723, Size of file: 16,05 KB
// Creation date: 2007-02-25 16:33
// Last modified: 2007-02-25 17:39
#endregion
#region Using directives
using SharpWars.Core.Graphics;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
#endregion

namespace SharpWars.Core.Network
{
	public class Client
	{
		private Dictionary<int, Player> players = new Dictionary<int, Player>();
		
		private Socket socket = null;
		
		private bool isConnected = false;
		
		private byte[] receivedData = new byte[Packet.MaxSize];
		
		public event StatusEventHandler Connected = null;
		
		public event StatusEventHandler Disconnected = null;
		
		public event PlayerEventHandler PlayerJoin = null;
		
		public event PlayerEventHandler PlayerDisconnected = null;
		
		public event PlayerEventHandler PlayerKicked = null;
		
		public event PlayerEventHandler PlayerListReceived = null;
		
		public event PlayerEventHandler PlayerReady = null;
		
		public event MessageEventHandler MessageReceived = null;
		
		public event LogEventHandler ConnectionFailed = null;
		
		public event ExceptionEventHandler Exception = null;
		
		public int PlayerID = 0;

		private string name = "Default Client";
		
		public void Connect( string localIP, int localPort, string serverIP, int serverPort )
		{
			if( !this.isConnected )
			{
				try
				{
					this.socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
					this.socket.DontFragment = true;
					this.socket.NoDelay      = true;
					this.socket.Bind( new IPEndPoint( IPAddress.Parse( localIP ), localPort ) );
					
					this.socket.BeginConnect( serverIP, serverPort, new AsyncCallback( OnConnect ), this.socket );
				}
				catch( SocketException ex )
				{
					OnConnectionFailed( ex.Message );
				}
				catch( Exception ex )
				{
					OnException( ex );
				}
			}
		}
		
		private void OnConnectionFailed( string reason )
		{
			if( this.ConnectionFailed != null )
			{
				this.ConnectionFailed( this, reason );
			}
		}
		
		private void OnConnect( IAsyncResult ar )
		{
			try
			{
				Socket socket = (Socket)ar.AsyncState;
				if( socket != null )
				{
					socket.EndConnect( ar );
					this.isConnected = true;
					
					socket.BeginReceive( this.receivedData, 0, this.receivedData.Length,
										 SocketFlags.None, new AsyncCallback( OnReceive ), socket );
				
					Packet packetToSend  = new Packet();
					packetToSend.Command = Command.Login;
					packetToSend.Write( this.name );
					
					Send( packetToSend );
				}
			}
			catch( SocketException ex )
			{
				OnConnectionFailed( ex.Message );
			}
			catch( Exception ex )
			{
				OnException( ex );
			}
		}
		
		private void OnException( Exception ex )
		{
			if( Exception != null )
			{
				Exception( this, new ExceptionEventArgs( ex.Message ) );
			}
		}
		
		private void OnReceive( IAsyncResult ar )
		{
			try
			{
				Socket socket = (Socket)ar.AsyncState;
				if( socket != null && this.isConnected )
				{
					bool logout = false;
					socket.EndReceive( ar );
				
					Packet receivedPacket = new Packet( this.receivedData );
					switch( receivedPacket.Command )
					{
						#region Welcome
					
						case Command.Welcome:
						{
							int playerID;
							receivedPacket.Read( out playerID );
							
							string name;
							receivedPacket.Read( out name );
							
							if( name == this.name )
							{
								this.PlayerID = playerID;
								
								if( Connected != null )
								{
									Connected( this, null );
								}
								
								Packet packetToSend  = new Packet();
								packetToSend.Command = Command.List;
								
								Send( packetToSend );
							}
							else
							{
								Player player   = new Player();
								player.Name     = name;
								player.PlayerID = playerID;
								player.Ready    = false;
								
								this.players.Add( playerID, player );
							
								if( PlayerJoin != null )
								{
									PlayerJoin( this, new PlayerEventArgs( playerID, name, false ) );
								}
							}
						}
						break;
						
						#endregion
						
						#region Logout
						
						case Command.Logout:
						{
							int playerID;
							receivedPacket.Read( out playerID );
							
							Player player = this.players[playerID];

							GameAction ga = new GameAction();
							ga.Action = EActionType.WT_PlayerDisconnect;
							ga.Player = playerID;
							GameMap.AddReceivedAction( ga );
							
							if( playerID == this.PlayerID )
							{
								logout = true;
								Logout();
							}
							else
							{
								this.players.Remove( playerID );
							
								if( PlayerDisconnected != null )
								{		
									PlayerDisconnected( this, new PlayerEventArgs(player.PlayerID, player.Name, player.Ready ) );
								}
							}
						}
						break;
						
						#endregion
						
						#region Message
						
						case Command.Message:
						{
							int sender;
							receivedPacket.Read( out sender );

							if( sender != this.PlayerID )
							{
								string message;
								receivedPacket.Read( out message );

								if( MessageReceived != null )
								{
									MessageReceived( this, new MessageEventArgs( sender, message ) );
								}
							}
						}
						break;
						
						#endregion
						
						#region List
						
						case Command.List:
						{
							int playerID;
							receivedPacket.Read( out playerID );
							
							string name;
							receivedPacket.Read( out name );
							
							bool ready;
							receivedPacket.Read( out ready );

							string remoteEndPoint;
							receivedPacket.Read( out remoteEndPoint );
							
							Player player         = new Player();
							player.Name           = name;
							player.PlayerID       = playerID;
							player.Ready          = ready;
							player.RemoteEndPoint = remoteEndPoint;
							
							this.players.Add( playerID, player );
							
							if( PlayerListReceived != null )
							{
								PlayerListReceived( this, new PlayerEventArgs( playerID, name, ready ) );
							}
						}
						break;
						
						#endregion
						
						#region Disconnect
						
						case Command.Disconnect:
						{
							logout = true;
							Logout();
						}
						break;
						
						#endregion
						
						#region Kick
						
						case Command.Kick:
						{
							int playerID;
							receivedPacket.Read( out playerID );
							
							if( playerID == this.PlayerID )
							{
								logout = true;
								Logout();
							}
							else
							{
								this.players.Remove( playerID );
							
								if( PlayerKicked != null )
								{
									PlayerKicked( this, new PlayerEventArgs( playerID, string.Empty, false ) );
								}
							}
						}
						break;
						
						#endregion
						
						#region Ready
						
						case Command.Ready:
						{
							int playerID;
							receivedPacket.Read( out playerID );
							
							Player player = this.players[playerID];
							player.Ready  = true;
						
							if( this.PlayerReady != null )
							{
								this.PlayerReady( this, new PlayerEventArgs( player.PlayerID, player.Name, true ) );
							}
						}
						break;
						
						#endregion
						
						#region SynchroAck
						
						case Command.SynchroAck:
						{
							GameAction ga = new GameAction();
							ga.Action = EActionType.WT_SynchroAck;
							
							GameMap.AddReceivedAction( ga );
						}
						break;
						
						#endregion

						#region SynchroRes

						case Command.SynchroReq:
						{
							SynchroRequest();
							break;
						} // block

						#endregion

						#region GameStart

						case Command.GameStart:
						{
							GameAction ga = new GameAction();
							ga.Action = EActionType.WT_GameStart;
							receivedPacket.Read( out ga.Player );
							receivedPacket.Read( out ga.Frame );
							
							GameMap.AddReceivedAction( ga );
						}
						break;
						
						#endregion
						
						#region MoveUnit
						
						case Command.MoveUnit:
						{
							GameAction ga = new GameAction();
							ga.Action     = EActionType.WT_Move;
							receivedPacket.Read( out ga.Player );
							receivedPacket.Read( out ga.Frame );
							receivedPacket.Read( out ga.Unit );
							
							float x;
							receivedPacket.Read( out x );
							
							float y;
							receivedPacket.Read( out y );
							
							ga.PtStart = new PointF( x, y );
							
							receivedPacket.Read( out x );
							receivedPacket.Read( out y );
							
							ga.PtDest = new PointF( x, y );
							
							GameMap.AddReceivedAction( ga );
						}
						break;
						
						#endregion
						
						#region AttackUnit
						
						case Command.AttackUnit:
						{
							GameAction ga = new GameAction();
							ga.Action     = EActionType.WT_Attack;
							receivedPacket.Read( out ga.Player );
							receivedPacket.Read( out ga.Frame );
							receivedPacket.Read( out ga.Unit );
							
							float x;
							receivedPacket.Read( out x );
							
							float y;
							receivedPacket.Read( out y );
							
							ga.PtStart = new PointF( x, y );
							
							GameMap.AddReceivedAction( ga );
						}
						break;
						
						#endregion
						
						#region PlayerDead
						
						case Command.PlayerDead:
						{
							GameAction ga = new GameAction();
							ga.Action     = EActionType.WT_DeadPlayer;
							receivedPacket.Read( out ga.Player );
							receivedPacket.Read( out ga.Frame );
							
							GameMap.AddReceivedAction( ga );
						}
						break;
						
						#endregion

						#region PlayerWin

						case Command.PlayerWin:
						{
							GameAction ga = new GameAction();
							ga.Action = EActionType.WT_PlayerWin;
							receivedPacket.Read( out ga.Player );
							receivedPacket.Read( out ga.Frame );

							GameMap.AddReceivedAction( ga );
						}
						break;

						#endregion

						#region SaveGame

						case Command.SaveGame:
						{
							GameAction ga = new GameAction();
							ga.Action = EActionType.WT_GameSave;
							receivedPacket.Read( out ga.Frame );
							receivedPacket.Read( out ga.Unit );

							string date;
							receivedPacket.Read( out date );

							ga.Custom = date;

							GameMap.AddReceivedAction( ga );

							break;
						} // block

						#endregion

						#region LoadGame

						case Command.LoadGame:
						{
							string saveGame;
							receivedPacket.Read( out saveGame );

							GameMap.instance.LoadGame( saveGame );

							break;
						} // block

						#endregion
					}
					
					if( !logout )
					{
						socket.BeginReceive( this.receivedData, 0, this.receivedData.Length,
											 SocketFlags.None, new AsyncCallback( OnReceive ), socket );
					}
				}
			}
			catch( Exception ex )
			{
				OnException( ex );
			}
		}
		
		private void Send( Packet packet )
		{
			try
			{
				this.socket.BeginSend( packet.RawBuffer, 0, packet.RawBuffer.Length, SocketFlags.None,
								       new AsyncCallback( OnSend ), this.socket );
			}
			catch( Exception ex )
			{
				OnException( ex );
			}
		}
		
		public void SendMessageToAll( string message )
		{
			Packet packetToSend  = new Packet();
			packetToSend.Command = Command.Message;
			packetToSend.Write( true );
			packetToSend.Write( message );
			
			Send( packetToSend );
		}
		
		public void MoveUnit( int frame, int player, int unit, PointF start, PointF destination )
		{
			Packet packetToSend  = new Packet();
			packetToSend.Command = Command.MoveUnit;
			packetToSend.Write( player );
			packetToSend.Write( frame );
			packetToSend.Write( unit );
			packetToSend.Write( start.X );
			packetToSend.Write( start.Y );
			packetToSend.Write( destination.X );
			packetToSend.Write( destination.Y );
			
			Send( packetToSend );
		}
		
		public void AttackUnit( int frame, int player, int unit, PointF start )
		{
			Packet packetToSend  = new Packet();
			packetToSend.Command = Command.AttackUnit;
			packetToSend.Write( player );
			packetToSend.Write( frame );
			packetToSend.Write( unit );
			packetToSend.Write( start.X );
			packetToSend.Write( start.Y );
			
			Send( packetToSend );
		}
		
		public void PlayerDead( int frame, int player )
		{
			Packet packetToSend  = new Packet();
			packetToSend.Command = Command.PlayerDead;
			packetToSend.Write( player );
			packetToSend.Write( frame );
			
			Send( packetToSend );
		}

		public void PlayerWin( int frame, int player )
		{
			Packet packetToSend = new Packet();
			packetToSend.Command = Command.PlayerWin;
			packetToSend.Write( player );
			packetToSend.Write( frame );

			Send( packetToSend );
		}
		
		public void Ready()
		{
			Packet packetToSend  = new Packet();
			packetToSend.Command = Command.Ready;
			packetToSend.Write( this.PlayerID );
			
			Send( packetToSend );
		}
		
		public void SynchroRequest()
		{
			Packet packetToSend  = new Packet();
			packetToSend.Command = Command.SynchroReq;
			packetToSend.Write( this.PlayerID );
			
			Send( packetToSend );
		}

		/// <summary>
		/// Save game
		/// </summary>
		/// <param name="frame">Frame</param>
		public void SaveGame( int frame )
		{
			Packet packetToSend  = new Packet();
			packetToSend.Command = Command.SaveGame;
			packetToSend.Write( frame );

			Send( packetToSend );
		} // SaveGame(frame)

		/// <summary>
		/// Load game
		/// </summary>
		/// <param name="saveGame">Save game</param>
		public void LoadGame( string saveGame )
		{
			Packet packetToSend = new Packet();
			packetToSend.Command = Command.LoadGame;
			packetToSend.Write( saveGame );

			Send( packetToSend );
		} // LoadGame(saveGame)
		
		private void OnSend( IAsyncResult ar )
		{
			try
			{
				Socket socket = (Socket)ar.AsyncState;
				if( socket != null && this.isConnected )
				{
					socket.EndSend( ar );
				}
			}
			catch( Exception ex )
			{
				OnException( ex );
			}
		}
		
		private Player CreatePlayer( PlayerEventArgs e )
		{
			Player client   = new Player();
			client.PlayerID = e.PlayerID;
			client.Name     = e.Name;
			client.Ready    = e.Ready;
			
			return client;
		}
		
		public void Disconnect()
		{
			try
			{
				if( this.isConnected )
				{
					this.isConnected = false;
					
					Packet packetToSend  = new Packet();
					packetToSend.Command = Command.Logout;
					
					Send( packetToSend );
					
					this.socket.Shutdown( SocketShutdown.Both );
					this.socket.Close();
					
					if( Disconnected != null )
					{
						Disconnected( this, null );
					}
				}
			}
			catch( Exception ex )
			{
				OnException( ex );
			}
		}
		
		private void Logout()
		{
			try
			{
				if( this.isConnected )
				{
					this.isConnected = false;
					
					this.socket.Shutdown( SocketShutdown.Both );
					this.socket.Close();
					
					if( Disconnected != null )
					{
						Disconnected( this, null );
					}
				
				}
			}
			catch( Exception ex )
			{
				OnException( ex );
			}
		}
		
		public string Name
		{
			get
			{
				return this.name;
			}
			set
			{
				this.name = value;
			}
		}
		
		public Dictionary<int, Player> Players
		{
			get
			{
				return this.players;
			}
		}
	}
}